import os
import json
import threading
import time
import hashlib
import requests
import traceback
from typing import Callable, Optional
from urllib3.util.retry import Retry
from requests.adapters import HTTPAdapter
from exceptions import NetworkError, BaiduApiError
from common import  with_retry # type: ignore
from common import ( # type: ignore
    CONFIG_DIR, SESSION_FILE, SESSION_EXPIRY_DAYS, COMMAND_LIST,
    DEFAULT_REMOTE_CWD, DEFAULT_DEBUG_MODE, ERROR_MESSAGES,
    SUCCESS_MESSAGES, INFO_MESSAGES, SYMBOLS
)

class BaiduPanCommands:
    def __init__(self, access_token, debug_mode):
        self.access_token = access_token
        self.debug_mode = debug_mode
        self.base_url = "https://pan.baidu.com/rest/2.0/xpan"
        # self.base_url = BAIDU_API_BASE_URL
        # 创建会话
        self.session = self._create_session()
        # 
        self.active_operations = {}   # 存储活跃操作
        self.lock = threading.Lock()  # 线程锁
        self.interrupt_flag = False   # 中断标志

        # 请求统计
        self.request_count = 0
        self.last_request_time = 0
        self.min_request_interval = 0.1  # 最小请求间隔100ms
        self.performance_stats = {
            'total_requests': 0,
            'failed_requests': 0,
            'total_time': 0,
            'avg_response_time': 0,
            'last_request_time': 0,
            'last_request_cost': 0
        }		
   
    def _create_session(self):
        """创建并配置会话"""
        session = requests.Session()
        
        # 配置重试策略
        retry_strategy = Retry(
            total=3,
            status_forcelist=[429, 500, 502, 503, 504],
            allowed_methods=["GET", "POST", "PUT", "DELETE"],
            backoff_factor=1
        )
        adapter = HTTPAdapter(
            max_retries=retry_strategy,
            pool_connections=10,
            pool_maxsize=20,
            pool_block=False
        )
        session.mount("http://", adapter)
        session.mount("https://", adapter)
        
        # 设置默认超时
        original_request = session.request
        def request_with_timeout(method, url, **kwargs):
            if 'timeout' not in kwargs:
                kwargs['timeout'] = (3.05, 27)
            return original_request(method, url, **kwargs)
        session.request = request_with_timeout
        
        return session

    def _paged_output(self, content, page_size=20):
        """分页输出大型内容，支持用户交互"""
        import shutil
        
        # 获取终端尺寸
        terminal_size = shutil.get_terminal_size()
        lines_per_page = page_size
        
        # 将内容分割成行
        if isinstance(content, dict) or isinstance(content, list):
            # 如果是JSON对象，先格式化
            import json
            formatted = json.dumps(content, indent=2, ensure_ascii=False)
            lines = formatted.split('\n')
        else:
            # 如果是字符串，直接分割
            lines = str(content).split('\n')
        
        total_lines = len(lines)
        current_line = 0
        
        print(f"📄 响应体大小: {total_lines} 行")
        print("⏎ 按回车继续，q 退出，a 显示全部")
        
        while current_line < total_lines:
            # 显示当前页
            end_line = min(current_line + lines_per_page, total_lines)
            for i in range(current_line, end_line):
                print(lines[i])
            
            current_line = end_line
            
            # 如果还有更多内容，等待用户输入
            if current_line < total_lines:
                try:
                    user_input = input(f"-- 更多 ({current_line}/{total_lines}) -- ").strip().lower()
                    if user_input == 'q':
                        print("输出已终止")
                        break
                    elif user_input == 'a':
                        # 显示剩余所有内容
                        for i in range(current_line, total_lines):
                            print(lines[i])
                        break
                    # 按回车继续显示下一页
                except KeyboardInterrupt:
                    print("\n输出已终止")
                    break
        
        print("-" * 60)

    def _debug_request(self, method, url, params, data, files):
        """调试输出请求信息"""
        print("=" * 60)
        print("🌐 HTTP 请求详情")
        print("=" * 60)
        print(f"方法: {method}")
        print(f"URL: {url}")
        print(f"参数: {json.dumps(params, indent=2, ensure_ascii=False)}")
        if data:
            print(f"数据: {json.dumps(data, indent=2, ensure_ascii=False)}")
        if files:
            print(f"文件: {files}")
        print("-" * 60)

    def _debug_response(self, response, request_time):
        """调试输出响应信息"""
        print(f"状态码: {response.status_code}")
        print(f"响应时间: {request_time:.3f}秒")
        print(f"响应头: {dict(response.headers)}")
                        
        # 尝试解析JSON响应
        try:
            result = response.json()
            
            # 检查响应体大小，决定是否分页显示
            result_str = json.dumps(result, indent=2, ensure_ascii=False)
            if len(result_str.split('\n')) > 20:  # 超过20行则分页显示
                self._paged_output(result)
            else:
                print(f"响应体: {result_str}")
        except:
            # 如果不是JSON，按文本处理
            text = response.text
            if len(text.split('\n')) > 20:  # 超过20行则分页显示
                self._paged_output(text)
            else:
                print(f"响应体: {text[:1000]}...")  # 限制输出长度
                        
        print("=" * 60)

    def _make_request(self, method, endpoint, data=None, params=None, files=None):
        """统一的请求方法"""
        # 控制请求频率
        current_time = time.time()
        elapsed = current_time - self.last_request_time
        if elapsed < self.min_request_interval:
            time.sleep(self.min_request_interval - elapsed)

        url = f"{self.base_url}/{endpoint}"

        # 添加访问令牌
        if params is None:
            params = {}
        params["access_token"] = self.access_token

        # 调试输出 - 请求信息
        if self.debug_mode:
            self._debug_request(method, url, params, data, files)

        start_time = time.time()
        try:
            response = self.session.request(
                method=method,
                url=url,
                params=params,
                data=data,
                files=files,
                timeout=30  # 添加超时设置
            )
            end_time = time.time()
            request_time = end_time - start_time

            # 调试输出 - 响应信息
            if self.debug_mode:
                self._debug_response(response, request_time)

            # 更新控制统计
            self.request_count += 1
            self.last_request_time = end_time
            self.performance_stats['total_requests'] += 1
            self.performance_stats['total_time'] += request_time
            self.performance_stats['avg_response_time'] = (
                self.performance_stats['total_time'] / self.performance_stats['total_requests']
            )
            self.performance_stats['last_request_time'] = end_time
            self.performance_stats['last_request_cost'] = request_time

            # 检查响应状态
            response.raise_for_status()
            
            # 解析JSON响应
            result = response.json()

            # 检查百度API错误码
            if 'errno' in result and result['errno'] != 0:
                self.performance_stats['failed_requests'] += 1
                errno = result['errno']
                _, errmsg = check_response(result)
                # 尝试刷新token
                if errno == 31045:  
                    if self._refresh_token():
                        # 更新token后重试
                        params["access_token"] = self.access_token
                raise BaiduApiError(errno, errmsg)

            return result
        except requests.exceptions.RequestException as e:
            raise NetworkError(f"网络请求失败: {e}")
        except ValueError as e:
            raise BaiduApiError(f"API响应解析失败: {e}")

    def get_performance_stats(self):
        """获取性能统计"""
        return self.performance_stats.copy()

    def _get_file_info(self, file_path):
        """
        从文件路径获取文件的属性
        
        参数:
            file_path: 文件的完整路径
            
        返回:
            文件的属性信息，如果找不到则返回{}
        """       
        # 分离目录路径和文件名
        dir_path = os.path.dirname(file_path)
        file_name = os.path.basename(file_path)
        
        if dir_path == "":
            dir_path = "/"
        
        try:
            # 列出目录内容
            result = self._list_files(dir_path)
            
            if 'list' not in result or not result['list']:
                print(f"❌ 目录为空或不存在: {dir_path}")
                return None
            
            # 在目录内容中查找匹配的文件
            for item in result['list']:
                # 检查文件名是否匹配且不是目录
                if (item['server_filename'] == file_name and 
                    item.get('isdir', 0) == 0):
                    return item
            
            print(f"❌ 文件不存在或不是普通文件: {file_path}")
            return {}
            
        except Exception as e:
            print(f"❌ 获取文件fsid失败: {e}")
            return {}

    @with_retry(max_retries=3, retry_delay=1, backoff_factor=2)
    def _list_files(self, path, order="name", start=0, limit=1000, folder=0):
        """列出网盘目录内容"""
        return self._make_request(
            "GET",
            "file",
            params={"method": "list", 
                "dir": path, 
                "order": order,
                "desc": 0,
                "start": start,
                "limit": limit,
                "folder": folder,
                "showempty": 1
                }
        )
	
    def list_files(self, path, order="name", start=0, limit=1000, folder=0) -> dict:
        """列出网盘目录内容"""
        try:
            return self._list_files(path, order, start, limit, folder)
        except Exception as e:
            print(f"列出网盘目录内容时报错: {e}")
            return {}
    
    @with_retry(max_retries=3, retry_delay=1, backoff_factor=2)
    def _create_folder(self, path):
        """创建网盘目录"""
        return self._make_request(
            "POST",
            "file",
            params={
                "method": "create"
            },
            data={
                "path": path,
                "isdir": 1
            }
        )
        
    def create_folder(self, path: str) -> bool:
        """创建网盘目录"""
        try:
            # 创建目录
            result = self._create_folder(path)
            success, error_msg = check_response(result)
            if not success:
                print(f"创建网盘目录时报错: {error_msg}")
                return False

            return True
        except Exception as e:
            print(f"创建网盘目录时报错: {e}")
            return False

    @with_retry(max_retries=1)    
    def _delete_file(self, path):
        """删除网盘文件或目录"""
        return self._make_request(
            "POST",
            "file",
            params = {
                "method": "filemanager",
                "opera": "delete"
            },
            data = {
                "async": 0,                     # 0 同步，1 自适应，2 异步
                "filelist": json.dumps([path]), # 待操作文件
                "ondup": "fail"                 # fail(默认，直接返回失败)
            }
        )
        
    def delete_file(self, path: str) -> bool:
        """删除网盘文件或目录"""
        try:
            # 删除网盘文件或目录
            result = self._delete_file(path)
            success, error_msg = check_response(result)
            if not success:
                print(f"删除网盘文件或目录时报错: {error_msg}")
                return False

            return True
        except Exception as e:
            print(f"删除网盘文件或目录时报错: {e}")
            return False
    
    
    @with_retry(max_retries=1)    
    def _move_file(self, source_path: str, dest_dir: str, new_name: str):
        """移动/重命名网盘文件"""
        return self._make_request(
            "POST",
            "file",
            params = {
                "method": "filemanager",
                "opera": "move"
            },
            data = {
                "filelist": json.dumps([{
                    "path": source_path,
                    "dest": dest_dir,
                    "newname": new_name
                }])
            }
        )
        
    def move_file(self, source_path: str, dest_dir: str, new_name: str) -> bool:
        """移动网盘文件"""
        try:
            # 移动网盘文件
            result = self._move_file(source_path, dest_dir, new_name)
            success, error_msg = check_response(result)
            if not success:
                print(f"移动网盘文件时报错: {error_msg}")
                return False

            return True
        except Exception as e:
            print(f"移动网盘文件时报错: {e}")
            return False

    @with_retry(max_retries=1)    
    def _rename_file(self, path: str, new_name: str):
        """重命名网盘文件或目录"""
        return self._make_request(
            "POST",
            "file",
            params = {
                "method": "filemanager",
                "opera": "rename"
            },
            data = {
                "async": 1,          # 0 同步，1 自适应，2 异步
                "filelist": json.dumps([{
                    "path": path,
                    "newname": new_name
                }])
            }
        )

    def rename_file(self, path: str, new_name: str) -> bool:
        """重命名网盘文件或目录"""
        try:
            # 重命名网盘文件或目录
            result = self._rename_file(path, new_name)
            success, error_msg = check_response(result)
            if not success:
                print(f"重命名网盘文件或目录时报错: {error_msg}")
                return False

            return True            
        except Exception as e:
            print(f"重命名网盘文件或目录时报错: {e}")
            return False

    @with_retry(max_retries=1)    
    def _copy_file(self, source_path: str, dest_dir: str, new_name: str):
        """复制网盘文件"""
        return self._make_request(
            "POST",
            "file",
            params = {
                "method": "filemanager",
                "opera": "copy"
            },
            data = {
                "filelist": json.dumps([{
                    "path": source_path,
                    "dest": dest_dir,
                    "newname": new_name
                }])
            }
        )

    def copy_file(self, source_path: str, dest_dir: str, new_name: str) -> bool:
        """复制网盘文件"""
        try:
            # 复制网盘文件
            result = self._copy_file(source_path, dest_dir, new_name)
            success, error_msg = check_response(result)
            if not success:
                print(f"复制网盘文件时报错: {error_msg}")
                return False

            return True    
        except Exception as e:
            print(f"复制网盘文件时报错: {e}")
            return False    

    @with_retry(max_retries=3, retry_delay=1, backoff_factor=2)
    def _search_files(self, keyword: str, path: str = "/"):
        """搜索网盘文件和目录"""
        return self._make_request(
            "GET",
            "file",
            params={
                "method": "search", 
                "key": keyword,
                "dir": path,
                "recursion": 1
                }
        )

    def search_files(self, keyword: str, path: str = "/") -> dict:
        """搜索网盘文件和目录"""
        try:
            # 搜索网盘文件和目录
            result = self._search_files(keyword, path)
            success, error_msg = check_response(result)
            if not success:
                print(f"搜索网盘文件和目录时报错: {error_msg}")
                return {}

            return result
        except Exception as e:
            print(f"搜索网盘文件和目录时报错: {e}")
            return {}


    def download_file_with_progress(self, fsid: str, local_path: str, progress_callback: Optional[Callable] = None) -> bool:
        """下载文件并支持进度回调"""
        try:
            # 获取下载链接
            url = f"{self.base_url}/multimedia?method=filemetas&access_token={self.access_token}"
            # 正确格式化 fsids 参数（JSON 数组字符串）
            fsids_json = json.dumps([int(fsid)])  # 确保 fsid 是整数
            params = {
                "fsids": fsids_json,
                "dlink": 1
            }
            response = requests.get(url, params=params)
            result = response.json()

            # 检查 API 响应
            if result.get('errno') != 0:
                print(f"API错误: {result.get('errmsg')} (错误代码: {result.get('errno')})")
                return False
            
            if 'list' not in result or not result['list']:
                print("获取文件元数据失败: 文件列表为空")
                return False
            
            file_info = result['list'][0]
            if 'dlink' not in file_info:
                print("文件信息中缺少下载链接")
                return False
			
            download_url = file_info['dlink']
            
            # 添加access_token到下载URL
            download_url = f"{download_url}&access_token={self.access_token}"

            # 下载文件 - 使用百度要求的User-Agent
            headers = {
                "User-Agent": "pan.baidu.com",  # 百度要求使用这个User-Agent
                "Referer": "https://pan.baidu.com/",
                "Accept": "*/*",
                "Accept-Encoding": "gzip, deflate, br",
                "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
                "Connection": "keep-alive"
            }
            # 添加重试机制
            max_retries = 5
            for attempt in range(max_retries):
                try:
                    response = requests.get(download_url, stream=True, headers=headers)
                    response.raise_for_status()
                    break
                except requests.exceptions.HTTPError as e:
                    if e.response.status_code == 403 and attempt < max_retries:
                        print(f"403错误，重试中 ({attempt+1}/{max_retries})...")
                        time.sleep(2 ** attempt)
                    else:
                        raise
            
            # 获取文件总大小
            total_size = int(response.headers.get('content-length', 0))
            if total_size == 0:
                total_size = file_info.get('size', 0)
        
            # 确保目录存在
            local_dir = os.path.dirname(local_path)
            if local_dir and not os.path.exists(local_dir):
                os.makedirs(local_dir, exist_ok=True)
            
            # 下载文件并更新进度
            downloaded = 0
            start_time = time.time()		
            with open(local_path, 'wb') as f:
                for chunk in response.iter_content(chunk_size=8192):
                    if chunk:
                        f.write(chunk)
                        downloaded += len(chunk)
                        if progress_callback:
                            progress_callback(len(chunk))

            # 验证下载文件大小
            if os.path.exists(local_path):
                actual_size = os.path.getsize(local_path)
                if total_size > 0 and actual_size != total_size:
                    print(f"警告: 下载文件大小不匹配 (预期: {total_size}, 实际: {actual_size})")
                    return False
            
            return True
        except KeyboardInterrupt:
            print("\n⚠️  下载已取消")
            return False
        except Exception as e:
            print(f"下载错误: {e}")
            import traceback
            traceback.print_exc()
            # 如果下载失败，删除可能已创建的部分文件
            if os.path.exists(local_path):
                os.remove(local_path)
            return False

    def upload_file_with_progress(self, local_path: str, remote_path: str, progress_callback: Optional[Callable] = None) -> bool:
        """上传文件并支持进度回调"""
        try:
            # 第1步：预上传
            precreate_url = f"{self.base_url}/file?method=precreate&access_token={self.access_token}"
            
            # 准备文件
            file_size = os.path.getsize(local_path)
            filename = os.path.basename(local_path)
            remote_dir = os.path.dirname(remote_path)

            # 计算文件分片MD5
            block_list = []
            with open(local_path, 'rb') as f:
                #chunk = f.read(4 * 1024 * 1024)  # 4MB分片
                for chunk in iter(lambda: f.read(4 * 1024 * 1024), b""): # 4MB分片
                    chunck_md5 = hashlib.md5(chunk).hexdigest()            
                    block_list.append(chunck_md5)
			
            # 提交预上传
            precreate_data = {
                "path": remote_path,
                "size": file_size,
                "isdir": 0,
                "autoinit": 1,
                "block_list": json.dumps(block_list)
            }
            precreate_response = requests.post(precreate_url, data=precreate_data)
            precreate_result = precreate_response.json()
            if precreate_result.get('errno') != 0:
                print(f"预上传失败: {precreate_result.get('errmsg', '未知错误')}")
                return False
            
            uploadid = precreate_result['uploadid']
            
            # 第2步：分片上传
            with open(local_path, 'rb') as f:
                part_seq = 0
                uploaded = 0
                
                while True:
                    chunk = f.read(4 * 1024 * 1024)  # 4MB分片
                    if not chunk:
                        break
                    
                    # 上传域名（应当获取上传域名）
                    upload_url = "https://d.pcs.baidu.com/rest/2.0/pcs/superfile2"
					
                    # 上传分片
                    upload_params = {
                        "method": "upload",
                        "access_token": self.access_token,
                        "type": "tmpfile",
                        "path": remote_path,
                        "uploadid": uploadid,
                        "partseq": part_seq
                    }
                    files = {'file': chunk}
                    upload_response = requests.post(upload_url, params=upload_params, files=files)
                    upload_result = upload_response.json()
                    
                    if upload_result.get('error_code'):
                        print(f"分片上传失败: {upload_result.get('error_msg', '未知错误')}")
                        return False
                    
                    uploaded += len(chunk)
                    part_seq += 1
                    
                    if progress_callback:
                        progress_callback(len(chunk))
            
            # 第3步：合并分片创建文件
            create_data = {
                "path": remote_path,
                "size": file_size,
                "isdir": 0,
                "block_list": json.dumps(block_list), # 文件各分片md5数组的json串
                "uploadid": uploadid,     # 预上传precreate接口下发的uploadid
                "rtype": 2,               # 2 为path冲突且block_list不同才重命名
            }
            create_url = f"{self.base_url}/file?method=create&access_token={self.access_token}"
            create_response = requests.post(create_url, data=create_data)
            create_result = create_response.json()

            # 检查创建结果
            if create_result.get('errno') != 0:
                print(f"创建文件失败: {create_result.get('errmsg', '未知错误')}")
                return False
            
            return True
        except Exception as e:
            print(f"上传错误: {e}")
            traceback.print_exc()
            return False

    def quota(self) -> dict:
        """获取网盘容量信息"""
        try:
            url = f"https://pan.baidu.com/api/quota?access_token={self.access_token}"
            payload = {
                "checkfree": 1,
                "checkexpire": 1
            }
            headers = {
                'User-Agent': 'pan.baidu.com'
            }
            response = requests.get(url, headers=headers, params=payload)
            result = response.json()
			
            # 检查响应
            success, error_msg = check_response(result)
            if not success:
                print(f"获取配额失败: {error_msg}")
                return {}

            return result
        except Exception as e:
            print(f"获取配额错误: {e}")
            return {}
    
    def user_info(self) -> dict:
        """获取用户信息"""
        try:
            url = f"{self.base_url}/nas?method=uinfo&vip_version=v2&access_token={self.access_token}"
            payload = {
                "vip_version": "v2"
            }
            response = requests.get(url, params=payload)
            result = response.json()

            # 检查响应
            success, error_msg = check_response(result)
            if not success:
                print(f"获取用户信息失败: {error_msg}")
                return {}

            return result
        except Exception as e:
            print(f"获取用户信息错误: {e}")
            return {}

def check_response(response):
    """
    检查百度网盘API响应并返回错误信息
    
    Args:
        response: API响应字典
        
    Returns:
        tuple: (是否成功, 错误消息)
    """
    # 检查响应是否为字典
    if not isinstance(response, dict):
        return False, "无效的API响应格式"

    # 检查是否有错误码
    errno = response.get('errno', 0)
    if errno == 0:
        return True, ""
    
    # 获取错误消息
    error_msg = response.get('errmsg', '未知错误')
    
    # 百度网盘官方错误码映射
    error_messages = {
        # 通用错误
        -1: "权益已过期",
        -3: "文件不存在",
        -6: "身份验证失败",
        -7: "文件或目录名错误或无权访问",
        -8: "文件或目录已存在",
        -9: "文件或目录不存在",
        
        # 参数错误
        2: "参数错误",
        31023: "参数错误",
        
        # 权限相关
        6: "不允许接入用户数据",
        20013: "权限不足，当前应用无接口权限",
        31024: "没有访问权限",
        31045: "access_token验证未通过",
        42213: "共享目录鉴权失败",
        
        # 文件操作相关
        10: "转存文件已经存在",
        12: "批量转存出错",
        255: "转存数量太多",
        31061: "文件已存在",
        31062: "文件名无效",
        31064: "上传路径错误",
        31066: "文件名不存在",
        31190: "文件不存在",
        31363: "分片缺失",
        42202: "文件个数超过相册容量上限",
        42210: "部分文件添加失败",
        42211: "获取图片分辨率失败",
        42212: "共享目录文件上传者信息查询失败",
        42214: "获取文件详情失败",
        
        # 上传相关
        111: "有其他异步任务正在执行",
        31299: "第一个分片的大小小于4MB",
        31355: "参数异常（uploadid问题）",
        31364: "超出分片大小限制",
        31365: "文件总大小超限",
        
        # 视频相关
        133: "播放广告",
        31301: "非音视频文件",
        31304: "视频格式不支持播放",
        31326: "命中防盗链",
        31338: "当前视频码率太高暂不支持流畅播放",
        31339: "非法媒体文件",
        31341: "视频正在转码",
        31346: "视频转码失败",
        31347: "当前视频太长，暂不支持在线播放",
        31649: "字幕不存在",
        50002: "播单id不存在",
        
        # 频率限制
        20012: "访问超限，调用次数已达上限，触发限流",
        31034: "命中接口频控",
        
        # 其他
        11: "用户不存在(uid不存在)",
        2131: "该分享不存在",
        20011: "应用审核中，仅限前10个完成OAuth授权的用户测试应用",
        31360: "url过期",
        31362: "签名错误",
        42905: "查询用户名失败",
    }
    
    # 获取错误消息，如果官方映射中有则使用，否则使用API返回的错误消息
    if errno in error_messages:
        error_msg = f"{error_messages[errno]} (错误代码: {errno})"
    else:
        error_msg = f"{error_msg} (错误代码: {errno})"
    
    # 添加请求ID（如果有）
    request_id = response.get('request_id')
    if request_id:
        error_msg += f" [请求ID: {request_id}]"
    
    return False, error_msg
